﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;

namespace FUSE.AzureConfig.Tasks
{
    public class FileGenerator
    {
        public const string DefaultEnvName = "__DEFAULT__";

        public const string CsdefName = "ServiceDefinition.csdef";
        public const string CscfgNameFmt = "ServiceConfiguration.{0}cscfg";

        public void AlterCsdef(AzureConfig config, string csdefPath, string outPath = null)
        {
            if (!Directory.Exists(csdefPath))
                throw new ArgumentException(csdefPath + " must exist");

            outPath = outPath ?? csdefPath;
            if (!Directory.Exists(outPath))
                throw new ArgumentException(outPath + " must exist");

            var doc = XDocument.Load(Path.Combine(csdefPath, CsdefName));
            var rootNS = GetRootNamespace(doc);
            foreach (var role in config.Roles)
            {
                var roleName = role.Value.RoleName;
                var roleNode = (from e in doc.Descendants()
                                   where (string)e.Attribute("name") == roleName
                                   select e).FirstOrDefault();
                if (roleNode == null)
                    throw new ArgumentException("Unable to find role " + roleName);
                else
                {
                    var settings = roleNode.Element(rootNS + "ConfigurationSettings");
                    if (settings == null)
                    {
                        Trace.TraceWarning("Adding ConfigurationSettings node for {0}", roleName);
                        settings = new XElement(rootNS + "ConfigurationSettings");
                        roleNode.Add(settings);
                    }
                    foreach (var setting in role.Value.ConfigurationSettingKeys.OrderBy(x => x))
                    {
                        AddOrUpdateSetting(settings, rootNS, setting);
                    }
                }
            }
            using (var writer = XmlWriter.Create(Path.Combine(outPath, CsdefName), new XmlWriterSettings() { Indent = true }))
                doc.WriteTo(writer);
        }

        public void AlterCscfgs(AzureConfig config, string cscfgPath, string outPath = null)
        {
            if (!Directory.Exists(cscfgPath))
                throw new ArgumentException(cscfgPath + " must exist");

            outPath = outPath ?? cscfgPath;
            if (!Directory.Exists(outPath))
                throw new ArgumentException(outPath + " must exist");

            foreach (var env in config.Environments)
            {
                var envName = env.Value.EnvName;
                // Load "baseline" cscfg
                var doc = XDocument.Load(Path.Combine(cscfgPath, string.Format(CscfgNameFmt, "")));
                var rootNS = GetRootNamespace(doc);
                foreach (var role in env.Value.Roles)
                {
                    var roleName = role.Value.RoleName;
                    var roleNode = (from e in doc.Descendants()
                                    where (string)e.Attribute("name") == roleName
                                    select e).FirstOrDefault();
                    if (roleNode == null)
                        throw new ArgumentException("Unable to find role " + roleName);
                    else
                    {
                        var instances = roleNode.Element(rootNS + "Instances");
                        if (instances == null)
                        {
                            Trace.TraceWarning("Adding Instances node for {0}", roleName);
                            instances = new XElement(rootNS + "Instances");
                            roleNode.AddFirst(instances);
                        }
                        var countAttr = instances.Attribute("count");
                        if (countAttr == null)
                        {
                            countAttr = new XAttribute("count", role.Value.Instances);
                            instances.Add(countAttr);
                        }
                        else
                        {
                            countAttr.Value = role.Value.Instances.ToString();
                        }

                        var settings = roleNode.Element(rootNS + "ConfigurationSettings");
                        if (settings == null)
                        {
                            Trace.TraceWarning("Adding ConfigurationSettings node for {0}", roleName);
                            settings = new XElement(rootNS + "ConfigurationSettings");
                            roleNode.Add(settings);
                        }
                        foreach (var setting in role.Value.ConfigSettings.Settings.OrderBy(x => x.Key))
                        {
                            AddOrUpdateSetting(settings, rootNS, setting.Key, setting.Value);
                        }
                        foreach (var setting in role.Value.ConfigSettings.MissingSettings.OrderBy(x => x))
                        {
                            AddOrUpdateSetting(settings, rootNS, setting, string.Empty);
                        }

                        var certs = roleNode.Element(rootNS + "Certificates");
                        if (certs == null)
                        {
                            Trace.TraceWarning("Adding Certificates node for {0}", roleName);
                            certs = new XElement(rootNS + "Certificates");
                            roleNode.Add(certs);
                        }
                        foreach (var cert in role.Value.Certificates)
                        {
                            AddOrUpdateCert(certs, rootNS, cert.Key, cert.Value.Thumbprint, cert.Value.ThumbprintAlgorithm);
                        }
                    }
                }
                var cscfgName = string.Format(CscfgNameFmt, envName == DefaultEnvName ? "" : envName + ".");
                using (var writer = XmlWriter.Create(Path.Combine(outPath, cscfgName), new XmlWriterSettings() { Indent = true }))
                    doc.WriteTo(writer);
            }
        }

        public static XNamespace GetRootNamespace(XDocument doc)
        {
            var namespaces = doc.Root.Attributes().Where(x => x.IsNamespaceDeclaration)
                .GroupBy(x => x.Name.Namespace == XNamespace.None ? string.Empty : x.Name.LocalName)
                .ToDictionary(x => x.Key, x => x.First());
            return namespaces[""].Value;
        }

        private void AddOrUpdateSetting(XElement settingsElt, XNamespace ns, string name, string value = null)
        {
            var existing = (from e in settingsElt.Elements()
                            where (string)e.Attribute("name") == name
                            select e).FirstOrDefault();
            if (value == null)
            {
                if (existing == null)
                    settingsElt.Add(new XElement(ns + "Setting", new XAttribute("name", name)));
            }
            else
            {
                if (existing == null)
                    settingsElt.Add(new XElement(ns + "Setting", new XAttribute("name", name), new XAttribute("value", value)));
                else
                {
                    var valueAttr = existing.Attribute(ns + "value");
                    if (valueAttr == null)
                        throw new ArgumentException("Passed in value, found Setting entry for " + name + ", but it had no value attribute");
                    valueAttr.Value = value;
                }
            }
        }

        private void AddOrUpdateCert(XElement certsElt, XNamespace ns, string name, string thumbprint, string thumbprintAlg)
        {
            var existing = (from e in certsElt.Elements()
                            where (string)e.Attribute("name") == name
                            select e).FirstOrDefault();
            if (existing == null)
                certsElt.Add(new XElement(ns + "Certificate", new XAttribute("name", name), 
                    new XAttribute("thumbprint", thumbprint), new XAttribute("thumbprintAlgorithm", thumbprintAlg)));
            else
            {
                existing.SetAttributeValue(ns + "thumbprint", thumbprint);
                existing.SetAttributeValue(ns + "thumbprintAlgorithm", thumbprintAlg);
            }
        }

    }
}
